package main.java.maziacs.characters;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import main.java.maziacs.Dice;
import main.java.maziacs.IBoard;
import main.java.maziacs.ITile;
import main.java.maziacs.squares.ISquare;
import main.java.maziacs.squares.IWhite;
import main.java.maziacs.squares.Square;


public class Maziacs extends Character implements IMaziacs {

	private static Integer numberOfMaziacs=0;
	private static Integer maziacsDead=0;
	private Integer idMaziac;
	private Integer distanceToHero;
	private List<ITile> pathToHero;
	private IBoard board;
	private boolean exito;

	private boolean dead;
	
	public Maziacs(ITile t){
		if(t.equals(null))
			throw new NullPointerException();//TODO
		
		pathToHero=new LinkedList<ITile>();
		numberOfMaziacs++;
		idMaziac=numberOfMaziacs;
		setDead(false);
		setBoard(t.getBoard());
		setPosition(new Position(t,t.getSquares()[1][1]));
	}
	
	public static Integer getMaziacsDead() {
		return maziacsDead;
	}

	public static void setMaziacsDead(Integer maziacsDead) {
		Maziacs.maziacsDead = maziacsDead;
	}

	

	/* (non-Javadoc)
	 * @see uml.IMaziacs#moveWithHero(uml.Hero)
	 */
	public void moveWithHero(IHero hero){

		if(hero.equals(null))
			throw new NullPointerException();//TODO
		Dice dice=new Dice(4);
		for(int i=1;i<=dice.rollingDice();i++){
			moveWithoutHero(hero);
		}
	}

			
				
	/* (non-Javadoc)
	 * @see uml.IMaziacs#moveWithoutHero(uml.Hero)
	 */
	public void moveWithoutHero(IHero hero){
		if(hero.equals(null))
			throw new NullPointerException();//TODO
		//Guarda las casillas a las que se puede mover el maziac
		Vector<ISquare> v=new Vector<ISquare>();
		//Posicion del Maziac i y j en la casilla antes de moverlo
		Integer posI=this.getPosition().getSquare().getPositionI();
		Integer posJ=this.getPosition().getSquare().getPositionJ();
		//Losetas adyacentes a la loseta en la que se encuentra el maziac
		Map<ITile, List<Integer>> map=this.getPosition().getTile().getAdjacentTiles();
		List<Integer> l=new LinkedList<Integer>();
		l.add(posI);
		l.add(posJ);
		//Casilla en la que se encuentra el maziac
		ISquare sq=this.getPosition().getSquare();
		//Se calcula la distancia al heroe
		Integer distance=calculateDistance(hero);
		//Se crea un maziac auxiliar a partir del maziac this
		Maziacs maziac=this;
		//Segun la casilla en la que se encuentre el maziac se determinan las casillas que acercan
		//al maziac al heroe
		if(posI==2 || posI==0 || posJ==2 || posJ==0){
			for(ITile t: map.keySet()){
				if(map.get(t).equals(l)){
					v.add(new Square(t));
					v.get(v.size()-1).setPositionI(t.getAdjacentTiles().get(this.getPosition().getTile()).get(0));
					v.get(v.size()-1).setPositionJ(t.getAdjacentTiles().get(this.getPosition().getTile()).get(1));
				}
			}
			v.add(this.getPosition().getTile().getSquares()[1][1]);
		}
		else if(posI==1 && posJ==1){
			v.add(this.getPosition().getTile().getSquares()[0][1]);
			v.add(this.getPosition().getTile().getSquares()[1][0]);
			v.add(this.getPosition().getTile().getSquares()[1][2]);
			v.add(this.getPosition().getTile().getSquares()[2][1]);
		}
		//Se recorre el vector de casillas a las que se puede mover el maziac
		//Se calcula cual le acerca al heroe y se selecciona esa como casilla destino
		for(ISquare s: v){
			if(!(s instanceof IWhite))
				v.remove(s);
			else{
				maziac.setPosition(new Position(s.getTile(), s));
				if (distance.compareTo(maziac.calculateDistance(hero))>0){
					distance=maziac.calculateDistance(hero);
					sq=s;
				}
			}
		}
		//Se modifica la posicion final del maziac
		this.setPosition(new Position(sq.getTile(), sq));
		board.getMaziacs().remove(this);
		board.getMaziacs().put(this, this.getPosition());
		/*
		 * Si al moverse el maziac, este llega a la casilla del heroe, luchan.
		 * Se crea una batalla y si el maziac pierde, se actualiza el atributo dead a true.
		 * Se ponen a null ciertos atributos del maziac, como la posicion del maziac en el tablero
		 * para que el heroe no pueda volver a encontrarselo.*/
		if(this.calculateDistance(hero)==0){
			battle=new Battle(hero, this);
			if(battle.getPj().equals(hero)){
				this.setDead(true);
				maziacsDead++;
				this.idMaziac=null;
				this.pathToHero=null;
				this.setPosition(null);
				board.getMaziacs().remove(this);
			}
		}
	}


	/**
	 * @uml.property  name="battle"
	 * @uml.associationEnd  multiplicity="(1 1)" inverse="maziacs:uml.Battle"
	 */
	private Battle battle;


	/* (non-Javadoc)
	 * @see uml.IMaziacs#getBattle()
	 */
	public Battle getBattle() {
		return battle;
	}


	/* (non-Javadoc)
	 * @see uml.IMaziacs#setBattle(uml.Battle)
	 */
	public void setBattle(Battle battle) {
		this.battle = battle;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#getNumberOfMaziacs()
	 */
	public static int getNumberOfMaziacs() {
		return numberOfMaziacs;
	}

	/**
	 * Setter of the property <tt>numberMaziacs</tt>
	 * @param numberMaziacs  The numberMaziacs to set.
	 * @uml.property  name="numberMaziacs"
	 */
	public static void setNumberOfMaziacs(int numberMaziacs) {
		numberOfMaziacs = numberMaziacs;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#calculateDistance(uml.Hero)
	 */
	public int calculateDistance(IHero hero){
		if(hero.equals(null))
			throw new NullPointerException();//TODO
		int distance=0;
		int distanceI=0;
		int distanceJ=0;
		List<Integer> l;
		/*Comprobamos si estan en la misma loseta el heroe y el maziac y calculamos 
		la distancia*/
		if(this.getPosition().getTile().equals(hero.getPosition().getTile())){
			distance=distanceSelfTile(hero);
		}
		/*Si no estan en la misma loseta */
		else{
			pathToHero.clear();
			pathToHero.add(this.getPosition().getTile());
			/*Llamamos al metodo tileDistance() para calcular la distancia en 
			 * losetas*/
			int tileDistance=TileDistance(hero.getPosition().getTile(), this.getPosition().getTile())-1;
			distance=3*tileDistance;
			/*Calculamos a partir de ahora las casillas de distancia en la loseta
			 * del heroe y la del maziac*/
			Map<ITile, List<Integer>> adTiles=this.getPosition().getSquare().getTile().getAdjacentTiles();
			for(ITile t: adTiles.keySet()){
				if(t.equals(pathToHero.get(0))){
					l=adTiles.get(t);
					distanceI=Math.abs(this.getPosition().getSquare().getPositionI()- l.get(0));
					distanceJ=Math.abs(this.getPosition().getSquare().getPositionJ()- l.get(1));
					distance=distance+distanceI+distanceJ;
				}
			}
			l=hero.getPosition().getTile().getAdjacentTiles().get(pathToHero.get(pathToHero.size()-2));
			distanceI=Math.abs(hero.getPosition().getSquare().getPositionI()- l.get(0));
			distanceJ=Math.abs(hero.getPosition().getSquare().getPositionJ()- l.get(1));
			distance=distance+distanceI+distanceJ;
		}
		
		return distance;
	}
	/**
	 * Calcula la distancia entre el heroe y el maziac cuando estan en la misma loseta.
	 * Es un metodo auxiliar de calculateDistance() 
	 */
	private int distanceSelfTile(IHero hero){
		if(hero.equals(null) || this.equals(null))
			throw new IllegalArgumentException();
		int distance, distanceI, distanceJ;
		distanceI=Math.abs(this.getPosition().getSquare().getPositionI()-hero.getPosition().getSquare().getPositionI());
		distanceJ=Math.abs(this.getPosition().getSquare().getPositionJ()-hero.getPosition().getSquare().getPositionJ());
		distance=distanceI+distanceJ;
		return distance;
	}

	/**
	 * Calcula la distancia entre el heroe y el maziac en numero de losetas para conocer
	 * la distancia total cuando no estan en la misma loseta
	 * Es un metodo auxiliar de calculateDistance() 
	 */
	private int TileDistance(ITile tile1, ITile tile2){
		if(tile1.equals(null) || tile2.equals(null))
			throw new IllegalArgumentException();
		exito=false;
		int tileDistance=0;
		/*Recorremos en profundidad las losetas adyacentes a la inicial(maziac) hasta
		 * encontrar la del heroe
		 * Si no se encuentra la del heroe se retrocede para probar por otro camino*/
		for(ITile t: tile2.getAdjacentTiles().keySet()){
			while(!exito){
				pathToHero.add(t);
				if (t.equals(tile1)){
					tileDistance=pathToHero.size();
					exito=true;
				}
				else{
					ITile t2=t;
					t2.getAdjacentTiles().remove(tile2);
					if(t2.getAdjacentTiles().size()>0)
						TileDistance(tile1, t2);
					if(!exito)
						pathToHero.remove(t);
				}
			}
			if(exito) break;
		}
		return tileDistance;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#seeHero(uml.Hero)
	 */
	public boolean seeHero(IHero hero){
	
		if(hero.equals(null) || this.equals(null))
			throw new NullPointerException();//TODO
		boolean see=false;		
		/*Comprobamos si estamos en el caso base*/
		if(baseCaseSeeHero(hero))
			see=true;
		/*Si no es el caso base*/
		while(!see){
			/*Comprobamos se estan sobre la misma coordenada i o j,
			 * si no lo esta , no lo puede ver y se sale del bucle*/
			if(!isInLine(hero))
				break;
			/*Comprobamos si lo ve*/
			if(lookDown(hero) || lookUp(hero) || lookLeft(hero) || lookRight(hero))
				see=true;
			
		}		
		return see;	
	}
	
	/**
	 * Comprueba si ve al heroe hacia la izquierda
	 */
	private Boolean lookLeft(IHero hero){
		if(hero.equals(null) || this.equals(null))
			throw new IllegalArgumentException();

		Boolean find=false;
		Maziacs maziac=this;
		Integer positionI=maziac.getPosition().getSquare().getPositionI();
		Boolean invalidSquare=false;
		Vector<Vector<ITile>> v=board.getTilesOnBoard();
		Integer tilePosI=maziac.getPosition().getTile().getPositionI();
		Integer tilePosJ=maziac.getPosition().getTile().getPositionJ();
		
		for(int j=tilePosJ;j>=0;j--){
			if(v.get(tilePosI).get(j)!=null){
				for(int sj=2;sj>=0;sj--){
					if(v.get(tilePosI).get(j).getSquares()[positionI][sj] instanceof IWhite){
						if(v.get(tilePosI).get(j).getSquares()[positionI][sj].equals(hero.getPosition().getSquare())){
							find=true;
							break;
						}
					}
					else{
						invalidSquare=true;
						break;
					}
				}
			}
			else{
				invalidSquare=true;
				break;
			}
			if(invalidSquare || find){
				break;
			}
		}
		return find;
	}

	/**
	 * Comprueba si ve al heroe hacia la derecha
	 */
	private Boolean lookRight(IHero hero){
		if(hero.equals(null) || this.equals(null))
			throw new IllegalArgumentException();

		Boolean find=false;
		Maziacs maziac=this;
		Integer positionI=maziac.getPosition().getSquare().getPositionI();
		Boolean invalidSquare=false;
		Vector<Vector<ITile>> v=board.getTilesOnBoard();
		Integer tilePosI=maziac.getPosition().getTile().getPositionI();
		Integer tilePosJ=maziac.getPosition().getTile().getPositionJ();
		
		for(int j=tilePosJ;j<v.get(tilePosI).size();j++){
			if(v.get(tilePosI).get(j)!=null){
				for(int sj=0;sj<3;sj++){
					if(v.get(tilePosI).get(j).getSquares()[positionI][sj] instanceof IWhite){
						if(v.get(tilePosI).get(j).getSquares()[positionI][sj].equals(hero.getPosition().getSquare())){
							find=true;
							break;
						}
					}
					else{
						invalidSquare=true;
						break;
					}
				}
			}
			else{
				invalidSquare=true;
				break;
			}
			if(invalidSquare || find){
				break;
			}
		}
		return find;
	}
	
	/**
	 * Comprueba si ve al heroe hacia la arriba
	 */
	private Boolean lookUp(IHero hero){
		if(hero.equals(null)|| this.equals(null))
			throw new IllegalArgumentException();
		Boolean find=false;
		Maziacs maziac=this;
		Integer positionJ=maziac.getPosition().getSquare().getPositionJ();
		Boolean invalidSquare=false;
		Vector<Vector<ITile>> v=board.getTilesOnBoard();
		Integer tilePosI=maziac.getPosition().getTile().getPositionI();
		Integer tilePosJ=maziac.getPosition().getTile().getPositionJ();
		
		for(int i=tilePosI;i>=0;i--){
			if(v.get(i).get(tilePosJ)!=null){
				for(int si=2;si>=0;si--){
					if(v.get(i).get(tilePosJ).getSquares()[si][positionJ] instanceof IWhite){
						if(v.get(i).get(tilePosJ).getSquares()[si][positionJ].equals(hero.getPosition().getSquare())){
							find=true;
							break;
						}
					}
					else{
						invalidSquare=true;
						break;
					}
				}
			}
			else{
				invalidSquare=true;
				break;
			}
			if(invalidSquare || find){
				break;
			}
		}
		return find;
	}

	/**
	 * Comprueba si ve al heroe hacia la abajo
	 */
	private Boolean lookDown(IHero hero){
		if(hero.equals(null) || this.equals(null))
			throw new IllegalArgumentException();

		Boolean find=false;
		Maziacs maziac=this;
		Integer positionJ=maziac.getPosition().getSquare().getPositionJ();
		Boolean invalidSquare=false;
		Vector<Vector<ITile>> v=board.getTilesOnBoard();
		Integer tilePosI=maziac.getPosition().getTile().getPositionI();
		Integer tilePosJ=maziac.getPosition().getTile().getPositionJ();
		
		for(int i=tilePosI;i<v.size();i++){
			if(v.get(i).get(tilePosJ)!=null){
				for(int si=0;si<3;si++){
					if(v.get(i).get(tilePosJ).getSquares()[si][positionJ] instanceof IWhite){
						if(v.get(i).get(tilePosJ).getSquares()[si][positionJ].equals(hero.getPosition().getSquare())){
							find=true;
							break;
						}
					}
					else{
						invalidSquare=true;
						break;
					}
				}
			}
			else{
				invalidSquare=true;
				break;
			}
			if(invalidSquare || find){
				break;
			}
		}
		return find;
	}
	
	/**
	 * Comprueba si el hero y el maziac se encuentran sobre la misma coordenada i o j 
	 * del tablero y de la loseta,
	 * en cuyo caso habria posibilidad de que se vean.
	 */
	private Boolean isInLine(IHero hero){
		if(hero.equals(null) || this.equals(null))
			throw new IllegalArgumentException();

		Boolean res=false;
		if((this.getPosition().getTile().getPositionJ().equals(hero.getPosition().getTile().getPositionJ()) &&
				this.getPosition().getSquare().getPositionJ().equals(1) && 
				hero.getPosition().getSquare().getPositionJ().equals(1)) || 
				(this.getPosition().getTile().getPositionI().equals(hero.getPosition().getTile().getPositionI()) &&
						this.getPosition().getSquare().getPositionI().equals(1) && 
						hero.getPosition().getSquare().getPositionI().equals(1))){
			res=true;
		}
		return res;
	}
	/**
	 * Calcula el caso base de que se vean el heroe y el maziac
	 * que es cuando la distancia es 0(estan en la misma casilla) o 1(casillas adyacentes)
	 */
	private Boolean baseCaseSeeHero(IHero hero){
		if(hero.equals(null) || this.equals(null))
			throw new IllegalArgumentException();

		Boolean see=false;
		if(calculateDistance(hero)==0 || calculateDistance(hero)==1)
			see=true;
		return see;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#setIdMaziac(java.lang.Integer)
	 */
	public void setIdMaziac(Integer idMaziac) {
		this.idMaziac = idMaziac;
	}
	
	/* (non-Javadoc)
	 * @see uml.IMaziacs#getIdMaziac()
	 */
	public Integer getIdMaziac() {
		return idMaziac;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#setDistanceToHero(java.lang.Integer)
	 */
	public void setDistanceToHero(Integer distanceToHero) {
		this.distanceToHero = distanceToHero;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#getDistanceToHero()
	 */
	public Integer getDistanceToHero() {
		return distanceToHero;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#setBoard(uml.Board)
	 */
	public void setBoard(IBoard board) {
		this.board = board;
	}
	
	/* (non-Javadoc)
	 * @see uml.IMaziacs#getBoard()
	 */
	public IBoard getBoard() {
		return board;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#setDead(boolean)
	 */
	public void setDead(boolean dead) {
		this.dead = dead;
	}

	/* (non-Javadoc)
	 * @see uml.IMaziacs#isDead()
	 */
	public boolean isDead() {
		return dead;
	}

}
